package pkg.andru.async;

import pkg.andru.collect.NonBlockingQueue;
import pkg.andru.concurrent.NonBlockingThreadPool;
import pkg.andru.util.Log;
import android.graphics.Bitmap;
import android.os.Process;

/**
 * 
 * @author idiottiger
 * 
 */
public class AsyncBitmapLoader {

    static final String LOG_TAG = "AsyncBitmapLoader";

    NonBlockingQueue<IBitmapLoader> mLoadingQueue;
    NonBlockingThreadPool mTreadPool;

    public static final int DEFAULT_LOADER_THREAD_SIZE = 2;
    public static final int DEFAULT_LOADER_QUEUE_SIZE = NonBlockingQueue.DEFAULT_SIZE;
    public static final int DEFAULT_CACHE_SIZE = 40;
    public static final int DEFAULT_BITMAP_LOAD_RETRY_TIMES = 0;

    static final int MSG_ON_LOAD_PRE = 1 << 20;
    static final int MSG_ON_LOAD_FAILD = MSG_ON_LOAD_PRE + 1;
    static final int MSG_ON_LOAD_FINISHED = MSG_ON_LOAD_FAILD + 1;
    static final int MSG_ON_LOAD_CANCELED = MSG_ON_LOAD_FINISHED + 1;

    int mLoaderQueueSize, mLoaderThreadSize, mLoadRetryTimes;

    private InnerMonitorThread mMonitorThread;

    volatile boolean isStoped;

    /**
     * use default loader queue size and loader thread size to init
     */
    public AsyncBitmapLoader() {
        this(DEFAULT_LOADER_QUEUE_SIZE, DEFAULT_LOADER_THREAD_SIZE, DEFAULT_BITMAP_LOAD_RETRY_TIMES);
    }

    /**
     * use the user's defined queue size and thread size to init
     * 
     * @param loaderQueueSize
     * @param loaderThreadSize
     * @param retryTimes
     * @throws IllegalArgumentException
     *             if theris size <= 0, will throw
     */
    public AsyncBitmapLoader(int loaderQueueSize, int loaderThreadSize, int retryTimes) {
        if (loaderQueueSize <= 0 || loaderThreadSize <= 0 || retryTimes < 0) {
            throw new IllegalArgumentException("queue, thread size need bigger than zero");
        }
        mLoaderQueueSize = loaderQueueSize;
        mLoaderThreadSize = loaderThreadSize;
        mLoadRetryTimes = retryTimes;

        mLoadingQueue = new NonBlockingQueue<IBitmapLoader>(mLoaderQueueSize);
        mLoadingQueue.clear();

        mTreadPool = new NonBlockingThreadPool(loaderThreadSize);
    }

    /**
     * start the loader, more time invoke just start once
     */
    public void startLoader() {
        if (mMonitorThread == null) {
            mMonitorThread = new InnerMonitorThread();
            mMonitorThread.start();
        }
    }

    /**
     * stop the loader, current bitmap load will be canceled
     */
    public void stopLoader() {
        if (mMonitorThread != null && !isStoped) {
            isStoped = true;
            mMonitorThread.wakeUpThread();
            mTreadPool.shutDown();
        }
    }

    /**
     * add bitmap loader to the loader queue
     * 
     * @param loader
     */
    public void addLoaderToQueue(IBitmapLoader loader) {
        synchronized (mLoadingQueue) {
            if (mMonitorThread != null && !isStoped && loader != null && !mLoadingQueue.contain(loader)) {
                mLoadingQueue.push(loader);
                mMonitorThread.wakeUpThread();
            }
        }
    }

    /**
     * common bitmap loader interface
     * 
     * @author idiottiger
     * 
     */
    public static interface IBitmapLoader {

        /**
         * actually load bitmap operation, this method dont run in the ui thread
         * 
         * @return bitmap
         */
        public Bitmap doLoadBitmap();
    }

    /**
     * inner class to queue the loader and execute the loader's method
     * 
     * @author idiottiger
     * 
     */
    class InnerMonitorThread extends Thread {
        @Override
        public void run() {
            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
            setName("bloader");
            while (!isStoped) {
                if (mLoadingQueue.isEmpty()) {
                    synchronized (mLoadingQueue) {
                        try {
                            mLoadingQueue.wait();
                        } catch (InterruptedException e) {
                        }
                    }
                }
                if (isStoped) {
                    break;
                }
                if (!mLoadingQueue.isEmpty() && !mTreadPool.isPoolFull()) {
                    final IBitmapLoader item = mLoadingQueue.pop();
                    if (item != null) {
                        Runnable runnable = new BitmapLoaderRunnable(item);
                        mTreadPool.execute(runnable);
                    }
                }
            }
        }

        public void wakeUpThread() {
            synchronized (mLoadingQueue) {
                mLoadingQueue.notify();
            }
        }

    }

    class BitmapLoaderRunnable implements Runnable {
        private IBitmapLoader mLoader;

        static final int SLEEP_TIME = 1000;

        public BitmapLoaderRunnable(IBitmapLoader loader) {
            mLoader = loader;
        }

        @Override
        public void run() {
            int retry = mLoadRetryTimes;
            while ((mLoader.doLoadBitmap() == null) && retry-- >= 0) {
                Log.w(LOG_TAG, "bitmap loader #doLoad return null, so retry");
                try {
                    Thread.sleep(SLEEP_TIME);
                } catch (InterruptedException e) {
                }
            }
        }
    }

}
